﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Text;
using FiniteStateMachine;
using Microsoft.CSharp;

namespace Workflows.Components.Rules
{
    /// <summary>
    /// parse rule design into call stacks
    /// </summary>
    public class RuleEngine
    {
        /// <summary></summary>
        public enum EvaluationResult
        {
            /// <summary>default</summary>
            NotDefined,
            /// <summary></summary>
            Negative,
            /// <summary></summary>
            Positive
        }
        public struct EvaluatorState
        {
            public Guid RuleConditionNodeID;
            public EvaluationResult EvaluationResult;
            public int State;
            public bool IsInit;
            public bool IsFinal;
        }

        #region members
        private Rule _Rule;
        /// <summary></summary>
        public Rule Rule
        {
            get
            {
                return this._Rule;
            }
            set
            {
                this._Rule = value;
            }
        }

        private FiniteStateMachineBase _FSM;
        /// <summary></summary>
        public FiniteStateMachineBase FSM
        {
            get
            {
                return this._FSM;
            }
            set
            {
                this._FSM = value;
            }
        }

        private Type _ObjTypeToEvaluate;
        /// <summary></summary>
        public Type ObjTypeToEvaluate
        {
            get { return _ObjTypeToEvaluate; }
            set { _ObjTypeToEvaluate = value; }
        }

        #endregion

        /// <summary>
        /// the only constructor
        /// </summary>
        /// <param name="rule"></param>
        public RuleEngine(Rule rule)
        {
            this._Rule = rule;
            Assembly assem = Assembly.ReflectionOnlyLoadFrom(this._Rule.AssemblyFilePath);
            this.ObjTypeToEvaluate = assem.GetType(this._Rule.BusinessObjectName);
            this._FSM = this.ParseRule(this._Rule);
        }

        private Assembly CreateInMemoryAssembly(string srcCode, string[] references)
        {
            CSharpCodeProvider provider=new CSharpCodeProvider();
            CompilerParameters param = new CompilerParameters();
            param.GenerateExecutable = false;
            param.IncludeDebugInformation = false;
            param.GenerateInMemory = true;
            if(references !=null && references.Length>0)
            {
                foreach(string reference in references)
                {
                    param.ReferencedAssemblies.Add(reference);        
                }
            }
            
            CompilerResults cr = provider.CompileAssemblyFromSource(param, srcCode);
            StringBuilder buffer=new StringBuilder();
            StringCollection output = cr.Output;
            if (cr.Errors.Count != 0)
            {
                Console.WriteLine("Error invoking scripts.");
                CompilerErrorCollection es = cr.Errors;
                foreach (CompilerError s in es)
                {
                    buffer.Append(s.ErrorText + Environment.NewLine);
                }
                Console.Write(output);
                throw new Exception(buffer.ToString());
            }
            else
            {
                return cr.CompiledAssembly;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public Dictionary<string,object> Evaluate(object obj)
        {
            Dictionary<string,object> ruleOutput=new Dictionary<string, object>();

            return ruleOutput;
        }

        #region parser
        private FiniteStateMachineBase ParseRule(Rule rule)
        {
            List<RuleNodeDataBase> ruleNodeDataList=new List<RuleNodeDataBase>();
            ruleNodeDataList.AddRange(rule.RuleNodes);
            StringBuilder code=new StringBuilder();
            ruleNodeDataList.AddRange(rule.RuleNodes);
            this.WalkEvaluationTree(rule.Start, rule.Finish.RuleNodeID, ruleNodeDataList, ref code);

            Assembly assem = this.CreateInMemoryAssembly(code.ToString(), new string[] {});
            FiniteStateMachineBase fsm = assem.CreateInstance(rule.RuleName) as FiniteStateMachineBase;
            return fsm;
        }

        private void WalkEvaluationTree(RuleNodeDataBase fromNode, Guid endNodeID, List<RuleNodeDataBase> allNodes, ref StringBuilder code)
        {
            if (fromNode.RuleNodeType == RuleNodeType.Conditional)
            {
                RuleNodeDataBase onFalseNode = null;
                RuleNodeDataBase onTrueNode = null;
                foreach (RuleNodeDataBase ruleNode in allNodes)
                {
                    if (ruleNode.RuleNodeID == fromNode.RuleNodeID)
                    {
                        if (ruleNode.FromNodeBranch.ToLower() == "false")
                        {
                            onFalseNode = ruleNode;
                        }
                        else
                        {
                            onTrueNode = ruleNode;
                        }
                    }
                }
                if (onFalseNode != null && onFalseNode.RuleNodeID != endNodeID)
                {

                }
                if (onTrueNode != null && onTrueNode.RuleNodeID != endNodeID)
                {

                }
            }
            else
            {
                RuleNodeDataBase toNode = null;
                foreach (RuleNodeDataBase ruleNode in allNodes)
                {
                    if (ruleNode.FromNodeID == fromNode.RuleNodeID)
                    {
                        toNode = ruleNode;
                        break;
                    }
                }
                if (toNode != null && toNode.RuleNodeID != endNodeID)
                {
                    // TODO: create function call and add to call queue

                    this.WalkEvaluationTree(toNode, endNodeID, allNodes, ref code);
                }
            }
        }
        #endregion

        #region states
        /// <summary>
        /// map rule condition id (Guid) to state ID (int)
        /// </summary>
        /// <param name="rule"></param>
        /// <returns></returns>
        private Dictionary<int, EvaluatorState> CreateStateEnum(Rule rule)
        {
            int lastState = 0;
            Dictionary<int, EvaluatorState> evaluatorStates = new Dictionary<int, EvaluatorState>();
            EvaluatorState emptyState = new EvaluatorState();
            emptyState.EvaluationResult = EvaluationResult.NotDefined;
            emptyState.RuleConditionNodeID = rule.Start.RuleNodeID;
            emptyState.State = lastState;
            emptyState.IsInit = true;
            emptyState.IsFinal = false;
            evaluatorStates.Add(lastState, emptyState);

            foreach (RuleNodeDataBase ruleNodeData in rule.RuleNodes)
            {
                if (ruleNodeData.RuleNodeType == RuleNodeType.Conditional)
                {
                    lastState++;
                    EvaluatorState evaluatorState = new EvaluatorState();
                    evaluatorState.State = lastState;
                    evaluatorState.RuleConditionNodeID = ruleNodeData.RuleNodeID;
                    evaluatorState.EvaluationResult = EvaluationResult.NotDefined;
                    evaluatorState.IsInit = false;
                    evaluatorState.IsFinal = false;
                    evaluatorStates.Add(lastState, evaluatorState);
                }
            }

            lastState++;
            EvaluatorState finalState = new EvaluatorState();
            finalState.RuleConditionNodeID = rule.Finish.RuleNodeID;
            finalState.State = lastState;
            finalState.EvaluationResult = EvaluationResult.NotDefined;
            finalState.IsInit = false;
            finalState.IsFinal = true;
            evaluatorStates.Add(lastState, finalState);
            return evaluatorStates;
        }
        #endregion

        #region condition assembly generator
        private object GenerateDeclarativeConditionClass(Assembly ruleAssembly, DeclarativeRuleCondition condition, string className)
        {
            return null;
        }

        private object GenerateCodeConditionClass(Assembly ruleAssembly, CodeRuleCondition condition, string className)
        {
            return null;
        }

        private object GenerateClassConditionClass(Assembly ruleAssembly, ClassRuleCondition condition, string className)
        {
            return null;
        }
        #endregion
    }
}
